package engine;

import java.util.HashMap;

import primitives.Patch;
import utility.Intersection;
import utility.Matrix4x4;
import utility.Vector3;
import Jama.Matrix;

public class Hemicube {

	private int facesWide;
	private double delta;
	public Vector3 eyePosition;
	public Vector3 normal;
	private HashMap<Patch,Double> formFactors;
	public Matrix4x4 rotMatrix;
	private Matrix4x4 localToWorld;
	
	Vector3[] frontPoints;
	Vector3 lowerLeft;
	Vector3 lowerRight;
	Vector3 upperLeft;
	Vector3 upperRight;
	
	public Hemicube(Patch parent, int facesWide) {
		this.facesWide = facesWide;
		delta = 1.0 / facesWide;
		formFactors = new HashMap<Patch,Double>();
		localToWorld = parent.getTransformMatrix();
		eyePosition = localToWorld.multiply(parent.getCenterPoint());
		Matrix4x4 translation = Matrix4x4.getTranslationMatrix(eyePosition.x(), eyePosition.y(), eyePosition.z());
		localToWorld = translation.multiply(localToWorld);
		
		normal = parent.normal();
		Vector3 xAxis = new Vector3(new double[]{1,0,0});
		Vector3 yAxis = new Vector3(new double[]{0,1,0});		
		/*Vector3 cAxis = normal;
		Vector3 aAxis = (normal.crossProduct(normal.minus(yAxis)).getMagnitude() > 0.00001) ? yAxis : xAxis;
		Vector3 bAxis = aAxis.crossProduct(cAxis);
		
		aAxis.normalize();
		bAxis.normalize();
		cAxis.normalize();
		double[][] elements = new double[][]{
							{ aAxis.x(), aAxis.y(), aAxis.z(), eyePosition.x()},
							{ bAxis.x(), bAxis.y(), bAxis.z(), eyePosition.y()},
							{ cAxis.x(), cAxis.y(), cAxis.z(), eyePosition.z()},
							{ 0,         0,         0,         1}};	
		rotMatrix = new Matrix4x4(elements);
		rotMatrix = rotMatrix.multiply(localToWorld);
		
		Vector3 cAxis = normal;
		Vector3 bAxis = parent.vertices[0];
		Vector3 aAxis = cAxis.crossProduct(bAxis);*/
		
		Vector3 cAxis = normal;
		Vector3 aAxis = (normal.crossProduct(normal.minus(yAxis)).getMagnitude() > 0.00001) ? yAxis : xAxis;
		Vector3 bAxis = aAxis.crossProduct(cAxis);
		aAxis.normalize();
		bAxis.normalize();
		cAxis.normalize();
		double[][] elements = new double[][]{
				{ aAxis.x(), aAxis.y(), aAxis.z(), 0},
				{ bAxis.x(), bAxis.y(), bAxis.z(), 0},
				{ cAxis.x(), cAxis.y(), cAxis.z(), 0},
				{ 0,         0,         0,         1}};	
		Matrix rotMatrix = new Matrix(elements);
		rotMatrix = rotMatrix.inverse();
		this.rotMatrix = new Matrix4x4(rotMatrix.getArray());
		renderFront();
		renderSides();
	}
	
	private void renderFront() {
		frontPoints = new Vector3[facesWide * facesWide];
		//System.out.println(rotationMatrix);
		lowerLeft  = rotMatrix.multiply(new Vector3(new double[]{-1, -1, 1}));
		lowerRight = rotMatrix.multiply(new Vector3(new double[]{ 1, -1, 1}));
		upperLeft  = rotMatrix.multiply(new Vector3(new double[]{-1,  1, 1}));
		upperRight = rotMatrix.multiply(new Vector3(new double[]{ 1,  1, 1}));

		//System.out.println();
		int i = 0;
		for(int t = 0; t < facesWide; t++) {
			for(int u = 0; u < facesWide; u++) {
				Vector3 leftT = lowerLeft.plus(upperLeft.minus(lowerLeft).scale(t * delta));
				Vector3 rightT = lowerRight.plus(upperRight.minus(lowerRight).scale(t * delta));
				Vector3 point = leftT.plus(rightT.minus(leftT).scale(u * delta));
				point = localToWorld.multiply(point);
				frontPoints[i++] = localToWorld.multiply(point);
				Intersection<Patch, Double> closest = Core.rc.castRay(eyePosition, point, Core.shapes);
				if(closest != null) {
					double count = (formFactors.containsKey(closest.patch)) ?
							formFactors.get(closest.patch) : 0;
					formFactors.put(closest.patch, 
							count + Core.DFF.front[t][u]);		
				} else {
					System.out.println(i);
				}
			}
		}
	}
	
	private void renderSides() {
		renderLeft();
		renderRight();
		renderUp();
		renderDown();
	}
	
	private void renderLeft() {
		Vector3 lowerFront = rotMatrix.multiply(
				new Vector3(new double[]{-1, -1, 0}));
		Vector3 lowerBack = rotMatrix.multiply(
				new Vector3(new double[]{-1, -1, 1}));
		Vector3 upperFront = rotMatrix.multiply(
				new Vector3(new double[]{-1,  1, 0}));
		Vector3 upperBack = rotMatrix.multiply(
				new Vector3(new double[]{-1,  1, 1}));
		for(int t = 0; t < facesWide; t++) {
			for(int u = 0; u < facesWide / 2; u++) {
				Vector3 leftT = lowerFront.plus(upperFront.minus(lowerFront).scale(t * delta));
				Vector3 rightT = lowerBack.plus(upperBack.minus(lowerBack).scale(t * delta));
				Vector3 point = leftT.plus(rightT.minus(leftT).scale(u * delta));
				point = localToWorld.multiply(point);
				Intersection<Patch, Double> closest = Core.rc.castRay(eyePosition, point, Core.shapes);
				if(closest != null) {
					double count = (formFactors.containsKey(closest.patch)) ?
							formFactors.get(closest.patch) : 0;
					formFactors.put(closest.patch, 
							count + Core.DFF.side[t][u]);		
				}
			}
		}
	}
	
	private void renderRight() {
		Vector3 lowerFront = rotMatrix.multiply(
				new Vector3(new double[]{-1, -1, 0}));
		Vector3 lowerBack = rotMatrix.multiply(
				new Vector3(new double[]{-1, -1, 1}));
		Vector3 upperFront = rotMatrix.multiply(
				new Vector3(new double[]{-1,  1, 0}));
		Vector3 upperBack = rotMatrix.multiply(
				new Vector3(new double[]{-1,  1, 1}));
		for(int t = 0; t < facesWide; t++) {
			for(int u = 0; u < facesWide / 2; u++) {
				Vector3 leftT = lowerFront.plus(upperFront.minus(lowerFront).scale(t * delta));
				Vector3 rightT = lowerBack.plus(upperBack.minus(lowerBack).scale(t * delta));
				Vector3 point = leftT.plus(rightT.minus(leftT).scale(u * delta));
				point = localToWorld.multiply(point);
				Intersection<Patch, Double> closest = Core.rc.castRay(eyePosition, point, Core.shapes);
				if(closest != null) {
					double count = (formFactors.containsKey(closest.patch)) ?
							formFactors.get(closest.patch) : 0;
					formFactors.put(closest.patch, 
							count + Core.DFF.side[t][u]);		
				}
			}
		}
	}
	
	private void renderUp() {
		Vector3 lowerFront = rotMatrix.multiply(
				new Vector3(new double[]{-1, -1, 0}));
		Vector3 lowerBack = rotMatrix.multiply(
				new Vector3(new double[]{-1, -1, 1}));
		Vector3 upperFront = rotMatrix.multiply(
				new Vector3(new double[]{-1,  1, 0}));
		Vector3 upperBack = rotMatrix.multiply(
				new Vector3(new double[]{-1,  1, 1}));
		for(int t = 0; t < facesWide; t++) {
			for(int u = 0; u < facesWide / 2; u++) {
				Vector3 leftT = lowerFront.plus(upperFront.minus(lowerFront).scale(t * delta));
				Vector3 rightT = lowerBack.plus(upperBack.minus(lowerBack).scale(t * delta));
				Vector3 point = leftT.plus(rightT.minus(leftT).scale(u * delta));
				point = localToWorld.multiply(point);
				Intersection<Patch, Double> closest = Core.rc.castRay(eyePosition, point, Core.shapes);
				if(closest != null) {
					double count = (formFactors.containsKey(closest.patch)) ?
							formFactors.get(closest.patch) : 0;
					formFactors.put(closest.patch, 
							count + Core.DFF.side[t][u]);		
				}
			}
		}
	}
	
	private void renderDown() {
		Vector3 lowerFront = rotMatrix.multiply(
				new Vector3(new double[]{-1, -1, 0}));
		Vector3 lowerBack = rotMatrix.multiply(
				new Vector3(new double[]{-1, -1, 1}));
		Vector3 upperFront = rotMatrix.multiply(
				new Vector3(new double[]{-1,  1, 0}));
		Vector3 upperBack = rotMatrix.multiply(
				new Vector3(new double[]{-1,  1, 1}));
		for(int t = 0; t < facesWide; t++) {
			for(int u = 0; u < facesWide / 2; u++) {
				Vector3 leftT = lowerFront.plus(upperFront.minus(lowerFront).scale(t * delta));
				Vector3 rightT = lowerBack.plus(upperBack.minus(lowerBack).scale(t * delta));
				Vector3 point = leftT.plus(rightT.minus(leftT).scale(u * delta));
				point = localToWorld.multiply(point);
				Intersection<Patch, Double> closest = Core.rc.castRay(eyePosition, point, Core.shapes);
				if(closest != null) {
					double count = (formFactors.containsKey(closest.patch)) ?
							formFactors.get(closest.patch) : 0;
					formFactors.put(closest.patch, 
							count + Core.DFF.side[t][u]);		
				}
			}
		}
	}
	
	public void renderSide(Vector3 corner1, Vector3 corner2, 
			Vector3 corner3, Vector3 corner4) {
		
	}
	
	public HashMap<Patch, Double> getFormFactors() {
		return formFactors;
	}
	
}
